package hydrap2p.helper;

import hydrap2p.Service;
import hydrap2p.ServiceLocation;

import java.util.HashMap;
import java.util.Map;

import org.apache.thrift.TException;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.transport.TSocket;

public abstract class Pinger {

	private final int wait;		// wait a number of milliseconds before pinging
	private final int interval;
	
	private PingerThread pinger;
	public Pinger(String name, ServiceLocation location)
	{
		this.interval=5000;
		this.wait=0;
		pinger=new PingerThread(name,location);
		new Thread(pinger, "PingerThread - "+name).start();
	}

	public Pinger(String name, ServiceLocation location, int interval)
	{
		this.interval=interval;
		this.wait=0;
		pinger=new PingerThread(name,location);
		Thread temp=new Thread(pinger, "PingerThread - "+name);
		temp.setDaemon(true);
		temp.start();
	}

	public Pinger(String name, ServiceLocation location, int interval, int wait)
	{
		this.interval=interval;
		this.wait=wait;
		pinger=new PingerThread(name,location);
		Thread temp=new Thread(pinger, "PingerThread - "+name);
		temp.setDaemon(true);
		temp.start();
	}
	
	/**
	 * Called when the timer times out
	 */
	public abstract void failure(String name);
	
	public void shutdown()
	{
		pinger.shutdown();
	}
	
	/**
	 * PingerThread periodically calls Ping on a Thrift Service
	 * If it fails, the failure() method will be called
	 * @author Hufman
	 *
	 */
	private class PingerThread implements Runnable {

		private TimeoutChecker timeouts;
		private String name;
		private ServiceLocation location;
		private TSocket transport;
		private boolean running;
		
		public PingerThread(String name, ServiceLocation location) {
			this.name=name;
			this.location=location;
			transport=new TSocket(location.address,location.port);
			running=true;
			
			timeouts=new TimeoutChecker(transport, (int)(interval*0.5));
			new Thread(timeouts,"Pinger TimeoutChecking - "+name).start();
		}
		@Override
		public void run() {
			try {
				Thread.sleep(wait);
			} catch (InterruptedException e1) {
				
			}
			while (running)
			{
				timeouts.startConnect();
				try {
					if (!transport.isOpen())
						transport.open();
					Service.Client connection=new Service.Client(new TBinaryProtocol(transport));
					connection.ping();
				} catch (Exception e) {
					failure(name);
					timeouts.shutdown();
					running=false;
				} 
				timeouts.finishedConnect();
				try {
					Thread.sleep(interval);
				} catch (InterruptedException e) {
					
				}
			}
		}
		
		public void shutdown()
		{
			timeouts.shutdown();
			running=false;
		}
	}
	/**
	 * PingerThread will manage a TimeoutChecker and pass a Socket
	 * PingerThread calls startConnect() before it's about to ping something
	 * PingerThread calls finishedConnect when the pinging finishes
	 * If the timeout occurs between these two events, it closes the socket
	 * This causes an error in the PingerThread to simulate a failure to ping
	 *
	 */
	private class TimeoutChecker extends Thread
	{
		private TSocket socket;
		private int timeout;
		private boolean running;
		private boolean testing;
		
		public TimeoutChecker(TSocket socket, int timeout)
		{
			this.socket=socket;
			running=true;
			this.timeout=timeout;
			setDaemon(true);
		}
		
		@Override
		public void run()
		{
			while(running)
			{
				synchronized(this)
				{
					try {
						this.wait();
					} catch (InterruptedException e1) {
						
					}
				}

				if (testing)
				{
					try {
						Thread.sleep(timeout);
					} catch (InterruptedException e) {
						// Finished
					}
				}
				
				if (running && testing)
				{
					socket.close();		// testing=true, so didn't finish
				}
			}
		}
		public void startConnect()
		{
			testing=true;
			synchronized(this)
			{
				this.notify();
			}
		}
		public void finishedConnect()
		{
			testing=false;
			this.interrupt();
		}
		
		public void shutdown()
		{
			running=false;
			testing=false;
			synchronized(this)
			{
				this.notify();
			}
			this.interrupt();
		}
		
	}
}
